﻿#region Librerias

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.IO;
using System.Runtime.Serialization;
using System.Text.RegularExpressions;
using System.Globalization;
using System.Threading;
using ESF.General.Entity;
using System.Xml;

#endregion


namespace ESF.General.Text
{
	[DataContract]
	[Serializable]
	public class FileData : IEquatable<FileData>
	{
		#region Constantes

		public const int MAX_ERROR_COUNT = 20;

		public const int CTRL_FILENAME_TYPE = 0;

		public const int CTRL_REG_TYPE = 1;

		#endregion


		#region Properties

		public CultureInfo CultureInfo { private set; get; }

		[DataMember]
		public bool Selected { set; get; }

		[DataMember]
		public FileRegisterValidatorSet ValidatorSet { set; get; }
		

		[IgnoreDataMember]
		public FileInfo FileInfo
		{
			get
			{
				FileHelper.WaitFileLocked (FileName);

				return new FileInfo(FileName);
			}
		}

		[DataMember]
		public string FileName { set; get; }


		[DataMember]
		public string IdentificationNumber { set; get; }

		[DataMember]
		public string IdentificationTypeCode { set; get; }

		[DataMember]
		public string IdentificatorNumber { set; get; }

		[DataMember]
		public string IdentificatorTypeCode { set; get; }


		[IgnoreDataMember]
		public Encoding FileEncoding { get { return TextHelper.GetFileEncoding (FileInfo.FullName); } }


		[IgnoreDataMember]
		public string EncodingName { get { return FileEncoding.EncodingName; } }


		[IgnoreDataMember]
		public bool IsValid 
		{ 
			get 
			{ 
				return (Errors.Count == 0);
			}
		}

		[IgnoreDataMember]
		public string MD5Hash
		{
			get
			{
				return TextHelper.GetMD5HashFile (FileName);
			}
		}


		[DataMember]
		public List<GenericValidationException> Errors { set; get; }
				

		[IgnoreDataMember]
		public string ErrorsXML
		{
			get
			{
				XmlWriter xmlWriter;
				
				StringBuilder stbXML;
				

				if (DataHelper.IsEmpty (Errors))
				{
					return null;
				}

				stbXML = new StringBuilder ();

				xmlWriter = XmlWriter.Create (stbXML, new XmlWriterSettings () { OmitXmlDeclaration = true });

				xmlWriter.WriteStartElement ("GenericValidationExceptions");

				foreach (GenericValidationException gve in Errors)
				{
					gve.ToXML (xmlWriter);
				}

				xmlWriter.WriteEndElement ();

				xmlWriter.Close ();

				return stbXML.ToString ();
			}
		}


		[IgnoreDataMember]
		public string OnlyName { get { return FileHelper.GetFileNameFirstPoint (Name); } }


		[IgnoreDataMember]
		public string OnlyExtension { get { return FileHelper.GetFileNameExtensionFromFirstPoint (Name); } }

		#endregion


		#region Lectura

		[IgnoreDataMember]
		public string Status 
		{ 
			get 
			{ 
				return (IsValid ? "OK" : "ER"); 
			}
		}

		[IgnoreDataMember]
		public DirectoryInfo Directory { get { return FileInfo.Directory; } }

		[IgnoreDataMember]
		public string Name { get { return FileInfo.Name; } }

		[IgnoreDataMember]
		public long Length { get { return FileInfo.Length; } }

		[IgnoreDataMember]
		public DateTime LastWriteTime { get { return FileInfo.LastWriteTime; } }

		[IgnoreDataMember]
		public DateTime CreationTime { get { return FileInfo.CreationTime; } }

		#endregion


		#region Constructor

		public FileData(string pFileName, FileRegisterValidatorSet pValidatorSet, string pIdentificatorTypeCode = null, string pIdentificatorNumber = null, CultureInfo pCultureInfo = null)
		{
			if (pCultureInfo == null)
			{
				pCultureInfo = ReadConfig.CultureInfo;
			}

			FileName = pFileName;
			
			Errors = new List<GenericValidationException>();

			ValidatorSet = pValidatorSet;

			IdentificatorNumber = pIdentificatorNumber;

			IdentificatorTypeCode = pIdentificatorTypeCode;

			CultureInfo = pCultureInfo;
		}

		#endregion


		#region Methods

		public bool Validate ()
		{
			string [] registers;

			int regType;
			int intValue;

			int lastRegisterType = -1;
			int lineNumber = 0;

			double dblValue;

			List<FileRegisterValidator> currentValidators;
			List<FileRegisterValidator> ctrlValidators;
			
			FileRegisterValidator currentSumCtrlValidator;
			FileRegisterValidator currentValidator = null;

			string currentField;
			string temp;
			string tempOnlyName;

			DateTime tempDate;

			int globalSecuence = 0;
			int tempIndex;
			int tempIterator;

			Encoding currentEncoding;

			FileInfo currentFileInfo;


			#region Validacion Previa a Datos
			
			Thread.CurrentThread.CurrentCulture = CultureInfo;

			Errors.Clear ();

			currentFileInfo = FileInfo;
			

			if (!File.Exists (currentFileInfo.FullName))
			{
				Errors.Add (new GenericValidationException ("La ubicación del archivo no existe, debe restaurarlo o removerlo del listado", currentFileInfo.FullName)); 
				return false;
			}

			
			currentEncoding = FileEncoding;


			if (ValidatorSet == null) 
			{
				Errors.Add (new GenericValidationException ("El nombre del archivo no corresponde al de ningún Anexo Técnico o no le ha sido habilitado", currentFileInfo.FullName)); 
				return false;
			}


			#region Identificacion

			currentValidator = ValidatorSet.Validators.FindAll(
				fv => fv.RegisterType == CTRL_FILENAME_TYPE
				&& fv.IsIdentificationTypeCode).FirstOrDefault();

			if (currentValidator != null)
			{
				IdentificationTypeCode = FileHelper.GetFileNameFirstPoint(currentFileInfo.FullName);

				tempIndex = (from VS in ValidatorSet.Validators
							where 
								VS.RegisterType == currentValidator.RegisterType
								&& VS.Position < currentValidator.Position
							select 
								VS.Length.Value).Sum ();

				if (IdentificationTypeCode.Length >= tempIndex + currentValidator.Length)
				{
					IdentificationTypeCode = IdentificationTypeCode.Substring(tempIndex, currentValidator.Length.Value);
				}

				if (IdentificatorTypeCode != null)
				{
					if (ValidatorSet.RequiresEnterpriseIdentificationInFile 
						&& IdentificatorTypeCode != IdentificationTypeCode)
					{
						Errors.Add(new GenericValidationException(string.Format("El Tipo de Identificacion de la Entidad del Archivo es {0} pero se esperaba {1}", IdentificationTypeCode, IdentificatorTypeCode), currentFileInfo.FullName));
					}
				}
			}


			currentValidator = ValidatorSet.Validators.FindAll(
				fv => fv.RegisterType == CTRL_FILENAME_TYPE
				&& fv.IsIdentificationNumber).FirstOrDefault();

			if (currentValidator != null)
			{
				IdentificationNumber = FileHelper.GetFileNameFirstPoint (currentFileInfo.FullName);

				tempIndex = (from VS in ValidatorSet.Validators
							 where
								 VS.RegisterType == currentValidator.RegisterType
								 && VS.Position < currentValidator.Position
							 select
								 VS.Length.Value).Sum();

				if (IdentificationNumber.Length >= tempIndex + currentValidator.Length)
				{
					IdentificationNumber = IdentificationNumber.Substring(tempIndex, currentValidator.Length.Value);
				}

				if (IdentificatorNumber != null)
				{
					if (ValidatorSet.RequiresEnterpriseIdentificationInFile 
						&& IdentificatorNumber != IdentificationNumber
						&& TextHelper.GetInt(IdentificatorNumber) != TextHelper.GetInt(IdentificationNumber))
					{
						Errors.Add(new GenericValidationException(string.Format("El Número de Identificacion de la Entidad del Archivo es {0} pero se esperaba {1}",
							TextHelper.GetInt(IdentificationNumber), TextHelper.GetInt(IdentificatorNumber)), currentFileInfo.FullName));
					}
				}
			}

			#endregion


			if (currentFileInfo.Length > ValidatorSet.FileMaxLengthBytes)
			{
				Errors.Add (
					new GenericValidationException(string.Format ("El tamaño del archivo {0} es superior al permitido para publicar {1}", 
						TextHelper.ToStringBytes (currentFileInfo.Length), TextHelper.ToStringBytes (ValidatorSet.FileMaxLengthBytes)), currentFileInfo.FullName)); 
			}

			
			// Validar el conjunto de valores permitidos donde este definido segun la posicion
			if (!DataHelper.IsEmpty (ValidatorSet.OnlyNameAllowedValues))
			{
				tempOnlyName = OnlyName;
				tempIndex = 0;
				tempIterator = 0;

				foreach (KeyValuePair<int, string []> item in ValidatorSet.OnlyNameAllowedValues)
				{
					if (item.Key == 0) { break; }

					// del indice actual a la longitud (key)
					if (tempIndex + item.Key < tempOnlyName.Length)
					{
						temp = tempOnlyName.Substring (tempIndex, item.Key);

						if (!DataHelper.IsEmpty (item.Value) && !item.Value.Contains (temp))
						{
							Errors.Add (
								new GenericValidationException(
									string.Format (
										"El valor en el nombre del archivo [posicion: {0}, longitud: {1}] \"{2}\" no corresponde a uno de estos: {3}", 
											tempIterator + 1, 
											item.Key, 
											temp, 
											TextHelper.Concatenate(", ", item.Value)), currentFileInfo.FullName, null, tempIterator) { RelatedValue = temp });
						}

						tempIndex += item.Key;
						tempIterator++;
					}
				}
			}

			if (!Regex.IsMatch(OnlyName, "^" + ValidatorSet.FileNameRegularExpression + "$"))
			{
				Errors.Add(new GenericValidationException(string.Format("Para un archivo \"{0}\" su nombre \"{1}\" tiene partes que lo hacen inválido revise las condiciones del nombre en su Anexo Técnico", ValidatorSet.Name, OnlyName), currentFileInfo.FullName));
			}

			// Valida partes del nombre que deben ser fechas en formato yyyyMMdd
			if (!DataHelper.IsEmpty(ValidatorSet.OnlyDatePortions))
			{
				foreach (KeyValuePair<string, int> item in ValidatorSet.OnlyDatePortions)
				{
					tempOnlyName = OnlyName;

					if (item.Value + 8 > tempOnlyName.Length)
					{
						Errors.Add(new GenericValidationException(string.Format("No hay caracteres suficientes en el nombre del archivo [posicion: {0}] para el \"{1}\" debe corresponder a una fecha en formato yyyyMMdd, ejemplo: 20120831", item.Value, item.Key), currentFileInfo.FullName, null, item.Value));
					}

					temp = tempOnlyName.Substring(item.Value, 8);

					if (!DateTime.TryParseExact(temp, "yyyyMMdd", CultureInfo.InvariantCulture, DateTimeStyles.None, out tempDate))
					{
						Errors.Add(new GenericValidationException(string.Format("El valor [{2}] en el nombre del archivo [posicion: {0}] para el \"{1}\" no corresponde a una fecha válida", item.Value, item.Key, temp), currentFileInfo.FullName, null, item.Value) { RelatedValue = temp });
					}
				}
			}
				
			if (!TextHelper.IsIn (OnlyExtension, ValidatorSet.AcceptedExtensions))
			{
				Errors.Add (new GenericValidationException (string.Format ("La extensión del archivo \"{2}\" no corresponde a una aceptable segun su Anexo Tecnico \"{0}\": {1}", ValidatorSet.Name, TextHelper.Concatenate (",", ValidatorSet.AcceptedExtensions), OnlyExtension), currentFileInfo.FullName));
				return false;
			}

			if (!ValidatorSet.ValidateTextData)
			{
				return true;
			}

			if (currentEncoding != ValidatorSet.Encoding 
				&& currentEncoding != Encoding.UTF8 
				&& currentEncoding != Encoding.GetEncoding (1252))
			{
			    Errors.Add (new GenericValidationException (string.Format ("El archivo se espera en codificación {0} pero esta en {1}", ValidatorSet.Encoding.EncodingName, EncodingName), currentFileInfo.FullName));
			    return false;
			}

			#endregion

			#region Validacion de los Datos

			ctrlValidators = ValidatorSet.Validators.FindAll (fv => fv.RegisterType == CTRL_REG_TYPE).OrderBy (fv => fv.Position).ToList ();
			
			foreach (FileRegisterValidator val in ValidatorSet.Validators)
			{
				//val.calSecuence = 0;
				val.calSumValue = 0;
				val.calCurrentValueSum = 0;
				val.calRegisterCount = 0; 
			}


			foreach (string currentLine in TextHelper.ReadLines (currentFileInfo.FullName))
			{
				if (Errors.Count >= MAX_ERROR_COUNT)
				{
					break;
				}

				lineNumber++;

				#region Basic
				
				if (TextHelper.IsEmpty (currentLine)) 
				{
					Errors.Add(
						new GenericValidationException(
							"La linea esta vacia",
							currentFileInfo.FullName, lineNumber, 0));
					continue;
				}

				#endregion

				registers = currentLine.Split (ValidatorSet.Separator);

				#region Register Type

				if (registers [0].Contains (" "))
				{
					Errors.Add(
						new GenericValidationException(
							"El Tipo de Registro contiene espacios en blanco",
							currentFileInfo.FullName, lineNumber, 0) { RelatedValue = registers[0] });
				}

				if (!int.TryParse (registers [0], out regType))
				{
					Errors.Add (
						new GenericValidationException (
							"El Tipo de Registro no es un número entero", 
							currentFileInfo.FullName, lineNumber, 0) { RelatedValue = registers [0] });
					return false;
				}

				// si es la primer linea
				if (lineNumber == 1)
				{
					// si es la primer linea debe ser de un registro de control
					if (regType != CTRL_REG_TYPE)
					{
						Errors.Add(
							new GenericValidationException(
								string.Format ("No se hayo el Identificador del Registro de Control en la primer línea, se encontro: {0}", regType),
								currentFileInfo.FullName, lineNumber, 0) { RelatedValue = registers[0] });
						return false;
					}
				}
				// si no es la primer linea 
				else
				{
					//debe ser todo menos un registro de control
					if (regType == CTRL_REG_TYPE)
					{
						Errors.Add(
							new GenericValidationException(
								"El archivo tiene mas de un registro de control, solo debe tener uno en la primer linea",
								currentFileInfo.FullName, lineNumber, 0) { RelatedValue = registers[0] });
						return false;
					}
				}

				#endregion

				#region Initialize Validators

				currentValidators = ValidatorSet.Validators.FindAll (fv => fv.RegisterType == regType).OrderBy (fv => fv.Position).ToList ();

				if (DataHelper.IsEmpty (currentValidators))
				{
					Errors.Add (
						new GenericValidationException (
							string.Format ("El registro del tipo {0} no esta soportado para un archivo de {1}", regType, ValidatorSet.Name), 
							currentFileInfo.FullName, lineNumber, 1, regType.ToString ()));
					continue;
				}
							
				if (currentValidators.Count != (registers.Length - 1))
				{
					Errors.Add (
						new GenericValidationException (
							string.Format("El número de campos ({0}) especificados en el Anexo Técnico, no coincide con los campos reportados ({1})", 
								currentValidators.Count + 1, registers.Length),
								currentFileInfo.FullName, lineNumber, 1, regType.ToString ()));
					
					// Si NO hay suficientes para leer
					if (currentValidators.Count < (registers.Length - 1))
					{
						break;
					}
				}

				#endregion

				for (int regIndex = 1; regIndex <= currentValidators.Count; regIndex++)
				{
					currentValidator = ValidatorSet.Validators.Find (fv => fv.RegisterType == lastRegisterType && fv.Position == regIndex);

					currentValidator = currentValidators [regIndex - 1];

					if (regIndex >= registers.Length)
					{
						Errors.Add(
							new GenericValidationException(
								string.Format("El número de campos ({0}) especificados en el Anexo Técnico, no coincide con los campos reportados ({1})",
									currentValidators.Count + 1, registers.Length),
									currentFileInfo.FullName, lineNumber, regIndex, regType.ToString(), currentValidator.Name));

						break;
					}

					currentField = registers [regIndex];

					if (currentField.EndsWith(" ") || currentField.StartsWith(" "))
					{
						Errors.Add(
							new GenericValidationException(
								"El Campo contiene espacios en blanco al inicio o al final de su contenido",
								currentFileInfo.FullName, lineNumber, regIndex, regType.ToString(), currentValidator.Name, currentField));
					}

					try
					{
						currentValidator.Validate (currentFileInfo.FullName, regType, regIndex, lineNumber, currentField);
					}
					catch (GenericValidationException gEx)
					{
						Errors.Add (gEx);
					}
					catch(Exception pEx)
					{
						GenericValidationException tEx = new GenericValidationException ("Error de analisis del archivo", 
							currentFileInfo.FullName, lineNumber, regIndex + 1, regType.ToString (), currentValidator.Name, currentField);

						tEx.Data.Add ("Error", pEx.Message);

						Log.Error (pEx);

						Errors.Add (tEx);
					}

					#region Ctrl register

					if (regType == CTRL_REG_TYPE)
					{
						if (currentValidator.IsRegisterCount)
						{
							if (!int.TryParse (currentField, out intValue))
							{
								Errors.Add (
									new GenericValidationException (
										"El campo contiene el valor de un conteo de registros pero no tiene el formato de un número", 
										currentFileInfo.FullName, lineNumber, regIndex + 1, regType.ToString (), currentValidator.Name, currentField));
							}
							else
							{
								currentValidator.calRegisterCount = intValue;
							}
						}

						if (currentValidator.SumRegisterIndex != null)
						{
							if (!double.TryParse (currentField, out dblValue))
							{
								Errors.Add (
									new GenericValidationException (
										"El campo contiene el valor de un acumulado pero no tiene el formato de un número", 
										currentFileInfo.FullName, lineNumber, regIndex + 1, regType.ToString (), currentValidator.Name, currentField));
							}
							else
							{
								currentValidator.calSumValue = dblValue;
							}
						}
					}

					#endregion

					else
					{
						#region Suma

					currentSumCtrlValidator = ctrlValidators.Find (cv => cv.SumRegisterType == regType && cv.SumRegisterIndex == regIndex);

					if (currentSumCtrlValidator != null)
					{
						if (!double.TryParse (currentField, out dblValue))
						{
							Errors.Add (
								new GenericValidationException (
									"El campo es parte de un acumulado pero no tiene el formato de un número", 
									currentFileInfo.FullName, lineNumber, regIndex + 1, regType.ToString (), currentValidator.Name, currentField));
						}
						else
						{
							currentSumCtrlValidator.calCurrentValueSum += dblValue;
						}
					}
					
					#endregion

						#region Secuencia

					if (currentValidator.IsSequence)
					{
						if (!int.TryParse (currentField, out intValue))
						{
							Errors.Add (
								new GenericValidationException (
									"El valor hace parte de una secuencia y debe ser un número entero", 
									currentFileInfo.FullName, lineNumber, regIndex + 1, regType.ToString (), currentValidator.Name, currentField));
							continue;
						}

						if (/*currentValidator.calSecuence*/ globalSecuence + 1 != intValue)
 						{
							Errors.Add (
								new GenericValidationException (
									string.Format ("El valor [{0}] hace parte de una secuencia, en linea anterior es {1}", 
										intValue, globalSecuence /*currentValidator.calSecuence*/),
									currentFileInfo.FullName, lineNumber, regIndex + 1, regType.ToString (), currentValidator.Name, currentField)); 
							continue;
						}
						else
						{
							/*currentValidator.calSecuence*/ globalSecuence = intValue;
						}
					}

					#endregion
					}
				}

				lastRegisterType = regType;
			}

			if (lineNumber == 0)
			{
				Errors.Add(
					new GenericValidationException(
						"El archivo no tiene lineas con contenido para leer, esta vacío",
							currentFileInfo.FullName, lineNumber));
				return false;
			}
			
			#region Sum Validation
				
			foreach (FileRegisterValidator valCtrl in ctrlValidators.FindAll (cv => cv.SumRegisterIndex != null))
			{
				if (Math.Round (valCtrl.calSumValue, 2) != Math.Round (valCtrl.calCurrentValueSum, 2))
				{
					if (Errors.Count == 0)
					{
						Errors.Add (
							new GenericValidationException (
								string.Format("La suma del registro de control (sumatoria campo {1}, registro tipo {0}) calculada es {2:0.0,0} no coincide con el valor reportado {3:0.0,0}",
									valCtrl.SumRegisterType, valCtrl.SumRegisterIndex,
									Math.Round(valCtrl.calCurrentValueSum, 2), Math.Round(valCtrl.calSumValue, 2)), 
									currentFileInfo.FullName, CTRL_REG_TYPE, valCtrl.Position, valCtrl.RegisterType.ToString (), valCtrl.Name, valCtrl.calSumValue.ToString ("0.0,0")));
					}
					else
					{
						Errors.Add(
							new GenericValidationException(
								string.Format("La suma del registro de control (sumatoria campo {1}, registro tipo {0}) no pudo ser calculada para compararla con el valor reportado {2:0.0,0} debido a que el archivo tiene errores en sus registros",
									valCtrl.SumRegisterType, valCtrl.SumRegisterIndex, valCtrl.calSumValue),
									currentFileInfo.FullName, CTRL_REG_TYPE, valCtrl.Position, valCtrl.RegisterType.ToString(), valCtrl.Name, valCtrl.calSumValue.ToString("0.0,0")));
					}
				}
			}

			#endregion

			#region Register Count Validation

			if (Errors.Count > MAX_ERROR_COUNT)
			{
				Errors.Add (new GenericValidationException (string.Format ("Se ha superado el límite máximo de errores ({0}) en el archivo. Debe corregirlo para procesarlo nuevamente.", MAX_ERROR_COUNT), currentFileInfo.FullName));
			}
			else
			{
				foreach (FileRegisterValidator valCtrl in ctrlValidators.FindAll(cv => cv.IsRegisterCount))
				{
					if (valCtrl.calRegisterCount != lineNumber - 1)
					{
						Errors.Add(
							new GenericValidationException(
								string.Format("El conteo de registros calculado es {0} no coincide con el valor reportado {1}",
									lineNumber - 1, valCtrl.calRegisterCount),
									currentFileInfo.FullName, CTRL_REG_TYPE, valCtrl.Position, valCtrl.RegisterType.ToString(), valCtrl.Name, valCtrl.calRegisterCount.ToString()));
					}
				}
			}

			#endregion


			#endregion

			return IsValid;
		}

		#endregion


		#region IEquatable Methods

		public bool Equals(FileData pOther)
		{
			if (pOther == null) { return false; }

			return String.Compare (pOther.FileInfo.FullName, FileInfo.FullName, true) == 0; 
		}

		#endregion


		#region ToString

		public override string ToString()
		{
			return FileInfo.FullName;
		}

		#endregion
	}
}
